---
title: "Grafast beta: last epic solved!"
description:
  "Polymorphism overhauled for greater efficiency and ergonomics. This is the
  last breaking change to plan resolvers we are expecting for v1."
slug: 2025-06-07-last-epic-solved
authors: [benjie, jem]
tags: [releases, v0.1]
---

import styles from "@site/src/css/common.module.css";

<div className={styles.intro}>

In the first Gra<em>fast</em> Working Group, we outlined 4 <em>major</em> issues in Gra<em>fast</em>
that needed to be addressed before we could think about general release. The fourth, and final,
epic has now been solved!

- ✅ Global dependencies — solved via “unary” steps
- ✅ Early exit — solved via “flags”
- ✅ Eradicating eval
- ✅ **Polymorphism — this release!**

In previous versions of Gra*fast* there was the possibility of exponential plan
branching due to the naive method of resolution of abstract types — a known
issue raised in the first Gra*fast* working group as one of four “epics” to be
solved before v1.0. This release of `grafast@0.1.1-beta.22` fixes this final epic through
a complete overhaul of the polymorphism system. Let’s take a look!

</div>

### Polymorphism epic achieved

By moving the responsibility of polymorphic resolution from field plan resolvers
into the abstract types themselves, we’ve centralized this logic, simplified
field plan resolvers, and unlocked more optimization opportunities and greater
execution efficiency. We no longer have the concept of “polymorphic capable”
steps: any step may now be used for polymorphism. Abstract types now gain a
`planType` method responsible for taking a “specifier” from the field plan
resolver and returning a step representing the name of its concrete object type
along with subplans for each possible object type.

To solve the problem of exponential branching, we merge the new specifier steps
from all previous polymorphic branches into a single “combined” step before
planning the next level of polymorphism.

<figure>

[![A plan diagram showing the old way polymorphism was handled. The nodes can branch exponentially.](../static/img/news/2025-06-06-combine-step-light-mode.png#light-mode-only)](../static/img/news/2025-06-06-combine-step-light-mode.png)
[![A plan diagram showing the new way, the branches are combined back together before moving to the next layer of resolution.](../static/img/news/2025-06-06-combine-step-dark-mode.png#dark-mode-only)](../static/img/news/2025-06-06-combine-step-dark-mode.png)

<figcaption>
  On the right is the new handling of polymorphic resolution.{" "}
  <code>getPetIds</code> and <code>getServiceAnimals</code> both fetch an{" "}
  <code>Animal</code> ID and so they are combined together in order to fetch all
  of the required Animals by their IDs. Once the IDs are fetched, the nodes can
  branch out to the different Animal types.
</figcaption>

</figure>

<!-- truncate-->

For the few of you who have been brave enough to hand write polymorphic plan
resolvers: first of all, thank you for trying it out! Hand written
polymorphic plan resolvers will need to be updated to match the new paradigm,
this will involve moving the polymorphic resolution from field plan resolvers
into the new `planType` method on the relevant abstract type (interface or union)
and adjusting the logic to fit the new pattern. Steps such as `polymorphicBranch`,
`pgPolymorphism`, and other polymorphism related steps no longer exist as they
are no longer supported in this new paradigm. For guidance on how to write the
`planType` method, see
[the updated polymorphism docs](../grafast/polymorphism) and please reach
out to us on Discord — we’d love to help you get migrated.

Excitingly, this is the last change to hand written plan resolvers that we
expect to make for the v1.0 release (other than some improvements around
TypeScript types), so we're getting close to release candidate stage!

### TypeDefs / plans overhaul

In order to make the libraries more type safe, `makeGrafastSchema` (from
`grafast`) and `makeExtendSchemaPlugin` (from `postgraphile/utils`) have
deprecated the `typeDefs`/`plans` pattern since `plans` (like `resolvers` in the
traditional format) ended up being a mish-mash of lots of different types
(objects, scalars, enums, etc) and `__`-prefixed fields (`__resolveType`,
`__isTypeOf`, etc) for methods on the type itself.

Going forwards, the configuration should be split into `typeDefs` with
`objects`, `interfaces`, `unions`, `inputObjects`, `scalars` and `enums` as
appropriate. Type-level properties such as
`resolveType`/`isTypeOf`/`planType`/`scope`/etc are no longer prefixed with `__`
and, to avoid conflicts with these type-level properties, object and input
object fields should be specified inside a new `plans` property and enum values
within the new `values` property.

**The old pattern will still work** (this is not a breaking change), but we
recommend moving to the new shape and will use it for all of our examples in the
documentation from now on.

Migration is quite straightforward:

1. **Add new top-level properties**. Add `objects`, `interfaces`, `unions`,
   `inputObjects`, `scalars`, and `enums` as top level properties alongside
   `typeDefs` and `plans`. Each should be an empty object. You can skip any
   where you’re not defining types of that kind.

1. **Split definitions based on type kind**. For each type defined in `plans`
   move it into the appropriate new object (based on keyword defining the type;
   i.e. `type` &rarr; `objects`, `interface` &rarr; `interfaces`, `union` &rarr;
   `unions`, `input object` &rarr; `inputObjects`, `scalar` &rarr; `scalars`,
   `enum` &rarr; `enums`).

1. **Move field plans into nested `plans: {...}` object**. For each type defined
   in the new `objects` and `inputObjects` objects: create a `plans: { ... }`
   entry inside the type and move all fields (anything not prefixed with `__`)
   inside this new (nested) property.

1. **Move enum values into nested `values: {...}` object**. For each type
   defined in the new `enums` object: create a `values: { ... }` entry inside
   the type and move all values (anything not prefixed with `__`) inside this
   new (nested) property.

1. **Remove `__` prefixes**. For each type across
   `objects`/`interfaces`/`unions`/`interfaceObjects`/`scalars` and `enums`:
   remove the `__` prefix from any methods/properties.

Example:

```diff
 typeDefs: ...,
-plans: {
+objects: {
   User: {
-    __isTypeOf(v) {
+    isTypeOf(v) {
       return v.username != null;
     },
    plans: {
       fieldName($source, fieldArgs) {
         // ...
       },
+    },
   },
+},
+interfaces: {,
   MyInterface: {
-    __resolveType($specifier) {
+    resolveType($specifier) {
       // ...
     }
   }
+},
+enums: {
   MyEnum: {
     ONE
     TWO
     THREE
   }
 },
```

_(Aside: we pasted the
[markdown version](https://github.com/graphile/graphile.github.io/blob/6693b91d5dd9980b676876524d0a14d370800dcf/src/news/2025-06-06-last-epic-solved.md#L78-L150)
of these instructions into ChatGPT and it managed to convert a number of plugins
perfectly! YMMV.)_

Other changes:

- `ObjectPlans`/`GrafastPlans`/`FieldPlans`/`InputObjectPlans`/`ScalarPlans` all
  changed to singular
- `InterfaceOrUnionPlans` split to `InterfacePlan`/`UnionPlan` (identical
  currently)
- Shape of `ObjectPlan`/`InterfacePlan`/`UnionPlan` has changed;
  `DeprecatedObjectPlan`/etc exist for back-compatibility
- `FieldArgs` can now accept an input shape indicating the args and their types
- `FieldPlanResolver<TArgs, TParentStep, TResultStep>` has switched the order of
  the first two generic parameters:
  `FieldPlanResolver<TParentStep, TArgs, TResultStep>` — this is to reflect the
  order of the arguments to the function. Also null has been removed from the
  generics.
- Various generics (including `GrafastFieldConfig`) that used to take a GraphQL
  type instance as a generic parameter no longer do — you need to use external
  code generation because TypeScript cannot handle the dynamic creation.
- `GrafastFieldConfig` last two generics swapped order.
- `GrafastArgumentConfig` generics completely changed

### New features

#### Steps

- `coalesce()`: Accepts a number of steps and represents the first value from
  them that isn’t `null` or `undefined`

#### Step classes

- Experimental support for adding “references” to other steps at plan-time only
  (via `refId = this.addRef($step)` and reciprocal `$step = this.getRef(refId)`
  methods). Useful for optimization; but use with great caution. Currently
  undocumented due to experimental nature.

### Improved type-safety

- `each()` now reflects the type of the list item even if it’s not a “list
  capable” step
- `loadOne()`/`loadMany()` can now track the underlying nullability of the
  callback

🚨 This will potentially break your plan types quite a bit. In particular, the
`LoadOneCallback` and `LoadManyCallback` types now have 5 (not 4) generic
parameters, the new one is inserted in the middle (after the second parameter)
and indicates the true return type of the callback (ignoring promises) — e.g.
`Maybe<ReadonlyArray<Maybe<ItemType>>>` for `LoadManyCallback`. They have
sensible defaults if you only specify the first two generics.

### And more besides...

In reaching this epic milestone, we have <strong>bumped the minimum version of node.js to
Node 22</strong> (the latest LTS); we have also found and fixed a number of other issues both
in Gra*fast* and the wider Graphile suite, you can see a full list at
[graphile.org](https://www.graphile.org/news/20250607-last-epic-solved/).

## Thank you Sponsors

Gra*fast* is crowd-funded open-source software, it relies on
crowd-sourced funding from individuals and companies to keep advancing.

If your company benefits from Gra*fast*, PostGraphile or the wider Graphile
suite, you should consider asking them to fund our work. By significantly
reducing the amount of work needed to achieve business goals and reducing
running costs, Graphile’s software results in huge time and money savings for
users. We encourage companies to contribute a portion of these savings back,
enabling the projects to advance more rapidly, and result in even greater
savings for your company.
[Find out more about sponsorship on graphile.org](https://graphile.org/sponsor/).

<figure>

![Thank you](../static/img/news/thank-you.svg)

</figure>
